package com.wewins.fota.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wewins.fota.bean.bo.cond.PlanCond;
import com.wewins.fota.bean.dto.CheckStatusReq;
import com.wewins.fota.bean.dto.CheckStatusResp;
import com.wewins.fota.bean.po.PkgVersion;
import com.wewins.fota.bean.po.UpdatePlan;
import com.wewins.fota.common.constant.CommonConst;
import com.wewins.fota.common.constant.RedisConst;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Stream;

@Service
public class RedisCacheService {
    private final RedisTemplate<String, Object> template;

    @Autowired
    public RedisCacheService(RedisTemplate<String, Object> template) {
        this.template = template;
    }

    public void cacheOnePkg(PkgVersion item) {
        String pkgDbKey = String.format(RedisConst.REDIS_KEY_PKG_BY_DB_ID, item.getId());
        this.template.opsForHash().putAll(
                pkgDbKey
                , JSON.parseObject(JSON.toJSONString(item), Map.class)
        );
        this.template.opsForSet().add(String.format(RedisConst.REDIS_KEY_PKG_CACHE, item.getProduct(), item.getVersion()), pkgDbKey);
    }

    public void cacheOnePlan(UpdatePlan plan) {
        String planDbKey = String.format(RedisConst.REDIS_KEY_PLAN_BY_DB_ID, plan.getId());
        this.template.opsForHash().putAll(
                planDbKey
                , JSON.parseObject(JSON.toJSONString(plan), Map.class)
        );
        this.template.opsForValue().set(String.format(RedisConst.REDIS_KEY_PLAN_MAP_BY_NAME, plan.getName()), planDbKey);
        plan.parseSrcVersions().forEach(srcV -> this.template.opsForSet().add(
                String.format(RedisConst.REDIS_KEY_PLAN_MAP_BY_PKG, plan.getProduct(), srcV)
                , planDbKey));
    }

    @Deprecated
    public void updatePlanStatus(UpdatePlan plan) {
        String planDbKey =String.format(RedisConst.REDIS_KEY_PLAN_BY_DB_ID, plan.getId());
        if (planDbKey != null) {
            this.template.opsForHash().put(planDbKey, "status", plan.getStatus());
            this.template.opsForHash().put(planDbKey, "updateTime", plan.getUpdateTime());
        }
    }

    public void removeOldCache4PlanById(int id) {
        String planDbKey = String.format(RedisConst.REDIS_KEY_PLAN_BY_DB_ID, id);
        if (planDbKey != null) {
            String planName = (String) this.template.opsForHash().get(planDbKey, "name");
            String product = (String) this.template.opsForHash().get(planDbKey, "product");
            String srcVStr = (String) this.template.opsForHash().get(planDbKey, "srcV");
            if (srcVStr != null) {
                Stream.of(srcVStr.split(",")).forEach(srcV -> this.template.opsForSet().remove(
                        String.format(RedisConst.REDIS_KEY_PLAN_MAP_BY_PKG, product, srcV)
                        , planDbKey));
            }
            this.template.delete(String.format(RedisConst.REDIS_KEY_PLAN_MAP_BY_NAME, planName));
            this.template.delete(planDbKey);
        }
    }

    public void removeOldCache4Plan(UpdatePlan plan) {
        removeOldCache4PlanById(plan.getId());
    }

    public void removeOldCache4PkgById(int id) {
        String pkgDbKey = String.format(RedisConst.REDIS_KEY_PKG_BY_DB_ID, id);
        String product = (String) this.template.opsForHash().get(pkgDbKey, "product");
        String version = (String) this.template.opsForHash().get(pkgDbKey, "version");
        String pkgMapKey = String.format(RedisConst.REDIS_KEY_PKG_CACHE, product, version);
        this.template.opsForSet().remove(pkgMapKey, pkgDbKey);
        this.template.delete(pkgDbKey);
    }

    public void removeOldCache4Pkg(PkgVersion version) {
        removeOldCache4PkgById(version.getId());
    }

    public void clearAllCache() {
        Set<String> keys = this.template.keys(RedisConst.REDIS_KEY_CACHE_PATTERN);
        if (keys != null && keys.size() > 0) {
            this.template.delete(keys);
        }
    }

    public CheckStatusResp checkStatus(CheckStatusReq req) {
        Set<Object> planDBKeys = this.template.opsForSet().members(
                String.format(
                        RedisConst.REDIS_KEY_PLAN_MAP_BY_PKG,
                        req.getProduct(),
                        req.getVersion()
                )
        );
        if (planDBKeys == null || planDBKeys.size() == 0) {
            return null;
        }
        Optional<PkgVersion> pkgOptional = planDBKeys.stream()
                .filter(planDbKey -> filterByCond(req, (String) planDbKey))
                .map(planDbKey -> getDstVList(req, (String) planDbKey))
                .filter(Objects::nonNull)
                .max(releaseTimeCompare());
        return pkgOptional.map(pkgVersion -> CheckStatusResp.builder().build().fillBy(pkgVersion, req.getLang())).orElse(null);
    }

    public Comparator<PkgVersion> releaseTimeCompare() {
        return (item1, item2) -> {  // 获取发布时间最新的目标版本
            if (item1.getReleaseTime() == item2.getReleaseTime()) {
                return 0;
            }
            return item1.getReleaseTime() > item2.getReleaseTime() ? 1 : -1;
        };
    }

    private PkgVersion getDstVList(CheckStatusReq req, String planDbKey) {
        String cond = (String) this.template.opsForHash().get(planDbKey, "cond");
        PlanCond planCond = JSON.parseObject(cond, PlanCond.class);
        Set<Object> pkgDbKeys = this.template.opsForSet().members(String.format(RedisConst.REDIS_KEY_PKG_CACHE,
                req.getProduct(),
                this.template.opsForHash().get(planDbKey, "dstV")
        ));
        if (pkgDbKeys == null || pkgDbKeys.size() == 0) {
            return null;
        }

        for (Object pkgDbKey: pkgDbKeys) {
            Map<String, String> flagMap = JSONObject.parseObject((String) this.template.opsForHash().get((String) pkgDbKey, "flag"), Map.class);
            if (isValidDstCond(planCond, flagMap)) {
                return JSON.parseObject(
                        JSON.toJSONString(
                                this.template.opsForHash().entries((String) pkgDbKey)
                        ),
                        PkgVersion.class
                );
            }
        }
        return null;
    }

    private boolean isValidDstCond(PlanCond planCond, Map<String, String> flagMap) {
        return (planCond == null && flagMap == null) || (planCond != null && planCond.accept(flagMap));
    }

    private boolean filterByCond(CheckStatusReq req, String planDbKey) {
        return isActive(planDbKey) && isValidCond(req, planDbKey);
    }

    private boolean isValidCond(CheckStatusReq req, String cacheKey) {
        String cond = (String) this.template.opsForHash().get(cacheKey, "cond");
        PlanCond planCond = JSON.parseObject(cond, PlanCond.class);
        return planCond == null || planCond.check(req);
    }

    private boolean isActive(String cacheKey) {
        int status = (int) template.opsForHash().get(cacheKey, "status");
        return status == CommonConst.PLAN_STATUS_ACTIVE;
    }
}
